
/* GCSx
** WSCROLL.H
**
** Widget parent class to provide frame/scrollbars
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

const string WidgetScroll::wtButtonUp("m");
const string WidgetScroll::wtButtonDown("n");
const string WidgetScroll::wtButtonLeft("l");
const string WidgetScroll::wtButtonRight("k");

WidgetScroll::WidgetScroll(FrameType type, Window* client, int cWidth, int cHeight, int cId) : Widget(cId, blankString, NULL) { start_func
    clientWidget = NULL;
    construct(type, client, cWidth, cHeight);
}

WidgetScroll::WidgetScroll(FrameType type, Widget* client, int cWidth, int cHeight) : Widget(client->getId(), blankString, NULL) { start_func
    clientWidget = client;
    construct(type, client, cWidth, cHeight);
}

void WidgetScroll::construct(FrameType type, Window* client, int cWidth, int cHeight) { start_func
    assert(cWidth >= -1);
    assert(cHeight >= -1);

    frameType = type;
    clientArea = client;
    dragMode = FRAME_DRAG_NONE;
    scrollX = scrollY = 0;
    hScrollLine = vScrollLine = fontHeight();
    scrollRepeatTicks = scrollRepeatDelay = 0;
    haveFocus = 0;
    whyDirty = FRAME_DIRTY_ALL;

    // Calculate our constants
    scrollbarButtonSize = fontHeight();
    scrollbarButtonXPad = (scrollbarButtonSize - fontWidth("f", FONT_WIDGET)) / 2;

    if ((frameType == FRAMETYPE_BEVEL) || (frameType == FRAMETYPE_BEVEL_BK)) {
        frameThickness = FRAME_BEVELTHICKNESS;
    }
    else if (frameType == FRAMETYPE_LINE) {
        frameThickness = FRAME_LINETHICKNESS;
    }
    else {
        frameThickness = 0;
    }

    if (client) {
        client->setParent(this);
        if (cWidth == -1) cWidth = client->getWidth();
        if (cHeight == -1) cHeight = client->getHeight();
    }
    else {
        if (cWidth == -1) cWidth = 0;
        if (cHeight == -1) cHeight = 0;
    }
    
    resize(cWidth + frameThickness * 2, cHeight + frameThickness * 2);
}

Widget* WidgetScroll::returnSelf() { start_func
    if (clientWidget) return clientWidget->returnSelf();
    return this;
}

void WidgetScroll::newClient(Window* fClient) { start_func
    assert(fClient);

    if (clientArea != NULL) {
        clientArea->setParent(NULL);
    }
    
    clientArea = fClient;
    clientWidget = NULL;
    clientArea->setParent(this);
    resize(width, height);
}

void WidgetScroll::newClient(Widget* fClient) { start_func
    assert(fClient);

    if (clientArea != NULL) {
        clientArea->setParent(NULL);
    }
    
    clientArea = clientWidget = fClient;
    clientArea->setParent(this);
    resize(width, height);
}

void WidgetScroll::newClient() { start_func
    if (clientArea != NULL) {
        clientArea->setParent(NULL);
    }
    
    clientArea = clientWidget = NULL;
}

void WidgetScroll::updateClientSize() { start_func
    resize(width, height);
}

void WidgetScroll::setScroll(int vLine, int hLine) { start_func
    assert(vLine > 0);
    assert(hLine > 0);
    
    vScrollLine = vLine;
    hScrollLine = hLine;
    
    // A page = full height/width, rounded down to a line multiple,
    // minus one line's worth
    vScrollPage = (clientHeightInner / vLine - 1) * vLine;
    if (vScrollPage < vLine) vScrollPage = vLine;
    hScrollPage = (clientWidthInner / hLine - 1) * hLine;
    if (hScrollPage < hLine) hScrollPage = hLine;
}

void WidgetScroll::resize(int newWidth, int newHeight, int newViewWidth, int newViewHeight, int fromParent) { start_func
    // (assert legal width/height)
    assert(newHeight >= 0);
    assert(newWidth >= 0);

    if (clientArea) {
        clientActualWidth = clientArea->getWidth();
        clientActualHeight = clientArea->getHeight();
    }
    else {
        clientActualWidth = clientActualHeight = 0;
    }

    // Our size (doesn't always set dirty, so we must)
    Window::resize(newWidth, newHeight, newViewWidth, newViewHeight, fromParent);
    setDirty();
    whyDirty = FRAME_DIRTY_ALL;

    // Determine client size
    newWidth -= frameThickness * 2;
    newHeight -= frameThickness * 2;
    clientX = frameThickness;
    clientY = frameThickness;

    // Scrollbars default to off    
    vScrollbarX = vScrollbarY = vScrollbarWidth = vScrollbarHeight = vScrollbarTabSize = 0;
    hScrollbarX = hScrollbarY = hScrollbarWidth = hScrollbarHeight = hScrollbarTabSize = 0;

    // Must have room for at least 3 button sizes, or no scrollbars appear
    if ((clientArea) && (newWidth >= scrollbarButtonSize * 3) && (newHeight >= scrollbarButtonSize * 3)) {
        int needH = clientActualWidth > newWidth;
        int needV = clientActualHeight > newHeight;
        if (needV) needH = clientActualWidth > newWidth - scrollbarButtonSize;
        if (needH) needV = clientActualHeight > newHeight - scrollbarButtonSize;
        
        if (needV) {
            vScrollbarX = clientX + newWidth - scrollbarButtonSize;
            vScrollbarY = clientY;
            vScrollbarWidth = scrollbarButtonSize;
            vScrollbarHeight = newHeight;
            if (needH) vScrollbarHeight -= scrollbarButtonSize;
        }
        
        if (needH) {
            hScrollbarX = clientX;
            hScrollbarY = clientY + newHeight - scrollbarButtonSize;
            hScrollbarWidth = newWidth;
            hScrollbarHeight = scrollbarButtonSize;
            if (needV) hScrollbarWidth -= scrollbarButtonSize;
        }
    }
    
    // Assign client size
    clientWidth = newWidth;
    clientHeight = newHeight;
    clientWidthInner = newWidth - vScrollbarWidth;
    clientHeightInner = newHeight - hScrollbarHeight;
    
    // Now we can determine scrollbar tab sizes
    // Determine tab size as the percent the current view is of the total view
    // Tab position determined by scrollTo call later
    if (vScrollbarX) {
        vScrollbarTabSize = clientHeightInner * (vScrollbarHeight - scrollbarButtonSize * 2) / clientActualHeight;
        if (vScrollbarTabSize < FRAME_SCROLLTABMIN) vScrollbarTabSize = FRAME_SCROLLTABMIN;
        if (vScrollbarTabSize > (vScrollbarHeight - scrollbarButtonSize * 2)) vScrollbarTabSize = 0;
    }

    if (hScrollbarY) {
        hScrollbarTabSize = clientWidthInner * (hScrollbarWidth - scrollbarButtonSize * 2) / clientActualWidth;
        if (hScrollbarTabSize < FRAME_SCROLLTABMIN) hScrollbarTabSize = FRAME_SCROLLTABMIN;
        if (hScrollbarTabSize > (hScrollbarWidth - scrollbarButtonSize * 2)) hScrollbarTabSize = 0;
    }

    // Ensure we're scrolled properly and sizing is set right
    scrollTo(scrollX, scrollY);
    setScroll(vScrollLine, hScrollLine);

    // Resize (move) client?
    if (clientArea) {
        clientArea->move(clientX + scrollX, clientY + scrollY);
        clientArea->resize(clientActualWidth, clientActualHeight, clientWidthInner, clientHeightInner, 1);
    }
}

WidgetScroll::~WidgetScroll() { start_func
    if (clientArea != NULL) {
        if (clientArea->wantsToBeDeleted()) delete clientArea;
        else clientArea->setParent(NULL);
    }
}

void WidgetScroll::childMoved(int fromX, int fromY, int toX, int toY, Window* child) { start_func
    setChildDirty();
}

void WidgetScroll::childResized(int fromW, int fromH, int toW, int toH, Window* child) { start_func
    setChildDirty();
}

void WidgetScroll::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // This widget breaks redraw down into scrollbars, edge, client area

        // Redraw all?
        if ((whyDirty == FRAME_DIRTY_ALL) || (totalDirty)) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }
        
        xOffset += x;
        yOffset += y;
        
        // Anything to draw so far?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
        
            if (disabled) SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);
        
            // Draw fill and border
            if (((frameType == FRAMETYPE_BEVEL) || (frameType == FRAMETYPE_BEVEL_BK)) && (clientWidth >= 0) && (clientHeight >= 0)) {
                drawGuiBoxInvert(xOffset, yOffset, width, height, FRAME_BEVELTHICKNESS, destSurface);
            }
            if ((frameType == FRAMETYPE_LINE) && (width > 0) && (height > 0)) {
                drawRect(xOffset, yOffset, width, height, guiPacked[COLOR_LINEBORDER], destSurface);
            }
        }
        
        // This will be the final displayed amount, we update it as we go but still work
        // off of the original dirtied area for efficiency
        Rect updatedArea = toDisplay;
        
        if (!disabled) {
            // (make sure vertical exists before doing more work)
            if (vScrollbarX) {
                // Determine clip area for vertical scrollbar
                Rect vertClip = { vScrollbarX + xOffset, vScrollbarY + yOffset, vScrollbarWidth, vScrollbarHeight };
                intersectRects(vertClip, clipArea);
            
                // If we need to update vertical scroll, OR it intersects with the dirty area,
                // redraw it; note that if the second conditional is tested, vertClip will be reduced
                // to whatever portion intersects with the dirty area, which is what we want
                if ((whyDirty & FRAME_DIRTY_SCROLLVERT) || (intersectRects(vertClip, toDisplay))) {
                    // Add vertical rect into updated area
                    boundRects(updatedArea, vertClip);
                
                    // Draw vertical scrollbar
                    SDL_SetClipRect(destSurface, &vertClip);
                    SDL_FillRect(destSurface, &vertClip, guiPacked[COLOR_SCROLLTRACK]);
                    if (vScrollbarTabSize) {
                        // Invert if pressed
                        if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_TAB)) {
                            drawRect(xOffset + vScrollbarX, yOffset + vScrollbarY + scrollbarButtonSize + vScrollbarTab, scrollbarButtonSize, vScrollbarTabSize, guiPacked[COLOR_FILL], destSurface);
                            drawGuiBoxInvert(xOffset + vScrollbarX, yOffset + vScrollbarY + scrollbarButtonSize + vScrollbarTab, scrollbarButtonSize, vScrollbarTabSize, 2, destSurface);
                        }
                        else {
                            drawGuiBox(xOffset + vScrollbarX, yOffset + vScrollbarY + scrollbarButtonSize + vScrollbarTab, scrollbarButtonSize, vScrollbarTabSize, 2, destSurface);
                        }
                    }
                    // if pressed
                    if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_LESS)) {
                        // Non-gradient: drawRect(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                        drawGradient(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                        drawGuiBoxInvert(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        drawText(wtButtonUp, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad + 1, yOffset + vScrollbarY + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                    }
                    else {
                        drawGuiBox(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        // Non-gradient: nothing
                        drawGradient(xOffset + vScrollbarX + 2, yOffset + vScrollbarY + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                        drawText(wtButtonUp, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad, yOffset + vScrollbarY + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                    }
                    // if pressed
                    if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_MORE)) {
                        // Non-gradient: drawRect(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                        drawGradient(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                        drawGuiBoxInvert(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        drawText(wtButtonDown, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad + 1, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                    }
                    else {
                        drawGuiBox(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        // Non-gradient: nothing
                        drawGradient(xOffset + vScrollbarX + 2, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                        drawText(wtButtonDown, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                    }
                }
            }
    
            // (make sure horizontal exists before doing more work)
            if (hScrollbarY) {
                // Determine clip area for horizontal scrollbar
                Rect horizClip = { hScrollbarX + xOffset, hScrollbarY + yOffset, hScrollbarWidth, hScrollbarHeight };
                intersectRects(horizClip, clipArea);
            
                // If we need to update horizontal scroll, OR it intersects with the dirty area,
                // redraw it; note that if the second conditional is tested, vertClip will be reduced
                // to whatever portion intersects with the dirty area, which is what we want
                if ((whyDirty & FRAME_DIRTY_SCROLLHORIZ) || (intersectRects(horizClip, toDisplay))) {
                    // Add horizontal rect into updated area
                    boundRects(updatedArea, horizClip);
                
                    // Draw horizontal scrollbar
                    SDL_SetClipRect(destSurface, &horizClip);
                    SDL_FillRect(destSurface, &horizClip, guiPacked[COLOR_SCROLLTRACK]);
                    if (hScrollbarTabSize) {
                        // Invert if pressed
                        if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_TAB)) {
                            drawRect(xOffset + hScrollbarX + scrollbarButtonSize + hScrollbarTab, yOffset + hScrollbarY, hScrollbarTabSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                            drawGuiBoxInvert(xOffset + hScrollbarX + scrollbarButtonSize + hScrollbarTab, yOffset + hScrollbarY, hScrollbarTabSize, scrollbarButtonSize, 2, destSurface);
                        }
                        else {
                            drawGuiBox(xOffset + hScrollbarX + scrollbarButtonSize + hScrollbarTab, yOffset + hScrollbarY, hScrollbarTabSize, scrollbarButtonSize, 2, destSurface);
                        }
                    }
                    // if pressed
                    if ((dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) && (dragItem == FRAME_SCROLL_LESS)) {
                        // Non-gradient: drawRect(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                        drawGradient(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                        drawGuiBoxInvert(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        drawText(wtButtonLeft, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + scrollbarButtonXPad + 1, yOffset + hScrollbarY + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                    }
                    else {
                        drawGuiBox(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        // Non-gradient: nothing
                        drawGradient(xOffset + hScrollbarX + 2, yOffset + hScrollbarY + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                        drawText(wtButtonLeft, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + scrollbarButtonXPad, yOffset + hScrollbarY + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                    }
                    // if pressed
                    if ((dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) && (dragItem == FRAME_SCROLL_MORE)) {
                        // Non-gradient: drawRect(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                        drawGradient(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                        drawGuiBoxInvert(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        drawText(wtButtonRight, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize + scrollbarButtonXPad + 1, yOffset + hScrollbarY + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                    }
                    else {
                        drawGuiBox(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                        // Non-gradient: nothing
                        drawGradient(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize + 2, yOffset + hScrollbarY + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                        drawText(wtButtonRight, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize + scrollbarButtonXPad, yOffset + hScrollbarY + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                    }
                }
            }
    
            // Fill in corner?
            if ((vScrollbarX) && (hScrollbarY)) {
                Rect cornerClip = { vScrollbarX + xOffset, hScrollbarY + yOffset, vScrollbarWidth, hScrollbarHeight };
                intersectRects(cornerClip, clipArea);
    
                // Only needed if area is dirty
                if (intersectRects(cornerClip, toDisplay)) {
                    // We set clip because it may have been reset earlier
                    SDL_SetClipRect(destSurface, &cornerClip);
                    SDL_FillRect(destSurface, &cornerClip, guiPacked[COLOR_FILL]);
                }
            }
    
            // Client area exists?
            if ((clientWidthInner > 0) && (clientHeightInner > 0)) {
                // Client area that CAN be drawn
                Rect clientClip = { clientX + xOffset, clientY + yOffset, clientWidthInner, clientHeightInner };
                intersectRects(clientClip, clipArea);
                
                // Client area that MUST be drawn
                Rect clientDirty = clientClip;
            
                // If we need to update client, OR it intersects with the dirty area, redraw it
                int clientIsDirty = 0;
                if ((clientArea) && (clientArea->isDirty())) clientIsDirty = 1;
                // We test dirty first, because we always want the dirty area to be what
                // the client HAS to update, even if they're dirty...
                if ((intersectRects(clientDirty, toDisplay)) || (clientIsDirty)) {
                    // ... but we clip to a wider area- the area the client CAN update
                    SDL_SetClipRect(destSurface, &clientClip);
                    
                    // Background fill on dirty area
                    SDL_FillRect(destSurface, &clientDirty, guiPacked[frameType == FRAMETYPE_BEVEL_BK ? COLOR_BKFILL : COLOR_TEXTBOX]);
    
                    // Paint client area
                    if (clientArea) {
                        clientArea->display(destSurface, clientDirty, clientClip, xOffset, yOffset);
                        // Add client rect into updated area
                        boundRects(updatedArea, clientDirty);
                    }
                }
            }
        }
        
        toDisplay = updatedArea;
    }

    dirty = 0;
    childDirty = 0;
    whyDirty = FRAME_DIRTY_NONE;
}

void WidgetScroll::scrollToView(int sX, int sY, int sWidth, int sHeight) { start_func
    int cX = -scrollX;
    int cY = -scrollY;
    // Don't adjust left-right if target encompasses entire view
    if ((sX > cX) || (sX + sWidth < cX + clientWidthInner)) {
        // If right-most edge of target is to the right of us, scroll to see it
        if (sX + sWidth > cX + clientWidthInner) cX = sX + sWidth - clientWidthInner;
    
        // Same for left
        if (sX < cX) cX = sX;
    }

    // Don't adjust up-down if target encompasses entire view
    if ((sY > cY) || (sY + sHeight < cY + clientHeightInner)) {
        // Same for bottom, top
        if (sY + sHeight > cY + clientHeightInner) cY = sY + sHeight - clientHeightInner;
        if (sY < cY) cY = sY;
    }
    
    // Complete scroll
    scrollTo(-cX, -cY);
}

void WidgetScroll::scrollBy(int sX, int sY) { start_func
    scrollTo(scrollX + sX, scrollY + sY);
}

void WidgetScroll::scrollTo(int sX, int sY, int tabX, int tabY) { start_func
    if (clientArea) {
        // Not too far in either direction
        if (clientActualWidth + sX < clientWidthInner) sX = clientWidthInner - clientActualWidth;
        if (clientActualHeight + sY < clientHeightInner) sY = clientHeightInner - clientActualHeight;

        // Check this direction last in case window is too large overall
        if (sX > 0) sX = 0;
        if (sY > 0) sY = 0;
        
        // Tab positions
        // Tab positions
        if ((vScrollbarTabSize) && (clientHeightInner < clientActualHeight)) {
            int newTab;
            if (tabY >= 0) newTab = tabY;
            else newTab = (vScrollbarHeight - (scrollbarButtonSize * 2) - vScrollbarTabSize) * sY / (clientHeightInner - clientActualHeight);
            if (newTab != vScrollbarTab) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLVERT;
            }
            vScrollbarTab = newTab;
        }
        else {
            if (vScrollbarTab != 0) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLVERT;
            }
            vScrollbarTab = 0;
        }
        if ((hScrollbarTabSize) && (clientWidthInner < clientActualWidth)) {
            int newTab;
            if (tabX >= 0) newTab = tabX;
            else newTab = (hScrollbarWidth - (scrollbarButtonSize * 2) - hScrollbarTabSize) * sX / (clientWidthInner - clientActualWidth);
            if (newTab != hScrollbarTab) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
            }
            hScrollbarTab = newTab;
        }
        else {
            if (hScrollbarTab != 0) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
            }
            hScrollbarTab = 0;
        }
        
        if ((scrollX != sX) || (scrollY != sY)) {
            scrollX = sX;
            scrollY = sY;
            clientArea->move(clientX + scrollX, clientY + scrollY);
        }
    }
    else {
        // No client, no scrollbars- reset to zero- dirty if wasn't already at zero
        if ((scrollX) || (scrollY)) {
            setDirty();
            whyDirty = FRAME_DIRTY_ALL;
            if (clientArea) clientArea->move(clientX, clientY);
        }
        scrollX = scrollY = 0;
    }
}

void WidgetScroll::whereCoords(int x, int y, DragType* dragType, DragSubType* dragItem) const { start_func
    assert(dragType);
    assert(dragItem);

    *dragType = FRAME_DRAG_NONE;
    *dragItem = FRAME_SCROLL_NONE;

    // Client area? Client *can* receive clicks outside it's actual area
    if ((clientArea) &&
        (x >= clientX) &&
        (y >= clientY) &&
        (x < clientX + clientWidthInner) &&
        (y < clientY + clientHeightInner)) {
        *dragType = FRAME_DRAG_CLIENT;
    }
    // Vertical scrollbar?
    else if ((x >= vScrollbarX) &&
        (x < vScrollbarX + vScrollbarWidth) &&
        (y >= vScrollbarY) &&
        (y < vScrollbarY + vScrollbarHeight)) {
        *dragType = FRAME_DRAG_SCROLLBAR_VERT;
        
        if (y < vScrollbarY + scrollbarButtonSize) *dragItem = FRAME_SCROLL_LESS;
        else if (y >= vScrollbarY + vScrollbarHeight - scrollbarButtonSize) *dragItem = FRAME_SCROLL_MORE;
        else if (y < vScrollbarY + scrollbarButtonSize + vScrollbarTab) *dragItem = FRAME_SCROLL_PAGELESS;
        else if (y >= vScrollbarY + scrollbarButtonSize + vScrollbarTab + vScrollbarTabSize) *dragItem = FRAME_SCROLL_PAGEMORE;
        else *dragItem = FRAME_SCROLL_TAB;
    }
    // Horizontal scrollbar?
    else if ((x >= hScrollbarX) &&
        (x < hScrollbarX + hScrollbarWidth) &&
        (y >= hScrollbarY) &&
        (y < hScrollbarY + hScrollbarHeight)) {
        *dragType = FRAME_DRAG_SCROLLBAR_HORIZ;

        if (x < hScrollbarX + scrollbarButtonSize) *dragItem = FRAME_SCROLL_LESS;
        else if (x >= hScrollbarX + hScrollbarWidth - scrollbarButtonSize) *dragItem = FRAME_SCROLL_MORE;
        else if (x < hScrollbarX + scrollbarButtonSize + hScrollbarTab) *dragItem = FRAME_SCROLL_PAGELESS;
        else if (x >= hScrollbarX + scrollbarButtonSize + hScrollbarTab + hScrollbarTabSize) *dragItem = FRAME_SCROLL_PAGEMORE;
        else *dragItem = FRAME_SCROLL_TAB;
    }
}

int WidgetScroll::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);

    DragType newDragMode;
    DragSubType newDragItem;
    int idleUsed = 0;
    Uint32 ticks;
    int mX, mY;
    int done = 0;
    Window* exempt = NULL;
    
    // We need to copy the event if we expect to change it to send a copy to client
    SDL_Event clientEvent = *event;

    switch (event->type) {
        case SDL_CLOSE:
            // Close client, then us
            if (clientArea) {
                int deleteClient = clientArea->wantsToBeDeleted();
                clientArea->event(hasFocus, &clientEvent);
                if (deleteClient) delete clientArea;
                else clientArea->setParent(NULL);
                clientArea = NULL;
            }
            return 1;
            
        case SDL_MOUSEBUTTONUP:
            if (dragMode == FRAME_DRAG_CLIENT) {
                // Adjust coordinates to 0-based for client
                if (clientArea) {
                    clientEvent.button.x -= clientX + scrollX;
                    clientEvent.button.y -= clientY + scrollY;

                    // Propogate to client
                    clientArea->event(hasFocus, &clientEvent);
                }

                if ((SDL_GetMouseState(NULL, NULL) & (SDL_BUTTON_LMASK | SDL_BUTTON_RMASK)) == 0) done = 1;
            }

            else if (event->button.button == SDL_BUTTON_LEFT) {
                if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                }
                else if (dragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLVERT;
                }

                done = 1;
            }
            
            if (done) {
                whereCoords(event->motion.x, event->motion.y, &newDragMode, &newDragItem);
                if (newDragMode != dragMode) selectMouse(MOUSE_NORMAL);
                dragMode = FRAME_DRAG_NONE;
                scrollRepeatTicks = scrollRepeatDelay = 0;
            }
            break;
        
        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
            // Dragging/clicking what?
            whereCoords(event->button.x, event->button.y, &newDragMode, &newDragItem);
            
            if (newDragMode == FRAME_DRAG_CLIENT) {
                // No drag on wheels
                if ((event->button.button != SDL_BUTTON_WHEELUP) &&
                    (event->button.button != SDL_BUTTON_WHEELDOWN)) {
                    dragMode = newDragMode;
                    dragItem = newDragItem;
                }

                // Adjust coordinates to 0-based for client
                if (clientArea) {
                    clientEvent.button.x -= clientX + scrollX;
                    clientEvent.button.y -= clientY + scrollY;

                    // Propogate to client
                    if (!clientArea->event(hasFocus, &clientEvent)) {
                        // If not used, handle mouse wheel ourselves
                        if (event->button.button == SDL_BUTTON_WHEELUP) {
                            scrollTo(scrollX, scrollY + vScrollPage);
                        }
                        else if (event->button.button == SDL_BUTTON_WHEELDOWN) {
                            scrollTo(scrollX, scrollY - vScrollPage);
                        }
                    }
                }
            }
            else if (((event->button.button == SDL_BUTTON_WHEELUP) || (event->button.button == SDL_BUTTON_WHEELDOWN)) &&
                     ((newDragMode == FRAME_DRAG_SCROLLBAR_VERT) || (newDragMode == FRAME_DRAG_SCROLLBAR_HORIZ))) {
                if (newDragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLVERT;
                    if (event->button.button == SDL_BUTTON_WHEELUP) scrollTo(scrollX, scrollY + vScrollPage);
                    else if (event->button.button == SDL_BUTTON_WHEELDOWN) scrollTo(scrollX, scrollY - vScrollPage);
                }
                if (newDragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                    if (event->button.button == SDL_BUTTON_WHEELUP) scrollTo(scrollX + hScrollPage, scrollY);
                    else if (event->button.button == SDL_BUTTON_WHEELDOWN) scrollTo(scrollX - hScrollPage, scrollY);
                }
            }
            else if (event->button.button == SDL_BUTTON_LEFT) {
                if (newDragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLVERT;
                    scrollRepeatTicks = SDL_GetTicks();
                    scrollRepeatDelay = DELAY_SCROLLBAR_REPEATSTART;
                    if (newDragItem == FRAME_SCROLL_LESS) scrollTo(scrollX, scrollY + vScrollLine);
                    else if (newDragItem == FRAME_SCROLL_MORE) scrollTo(scrollX, scrollY - vScrollLine);
                    else if (newDragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX, scrollY + vScrollPage);
                    else if (newDragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX, scrollY - vScrollPage);
                    // Force mouse motion to determine new drag mode
                    SDL_GetMouseState(&mX, &mY);
                    SDL_WarpMouse(mX, mY);
                }
                if (newDragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                    scrollRepeatTicks = SDL_GetTicks();
                    scrollRepeatDelay = DELAY_SCROLLBAR_REPEATSTART;
                    if (newDragItem == FRAME_SCROLL_LESS) scrollTo(scrollX + hScrollLine, scrollY);
                    else if (newDragItem == FRAME_SCROLL_MORE) scrollTo(scrollX - hScrollLine, scrollY);
                    else if (newDragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX + hScrollPage, scrollY);
                    else if (newDragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX - hScrollPage, scrollY);
                    // Force mouse motion to determine new drag mode
                    SDL_GetMouseState(&mX, &mY);
                    SDL_WarpMouse(mX, mY);
                }

                dragMode = newDragMode;
                dragItem = newDragItem;
                dragX = event->button.x;
                dragY = event->button.y;
                dragItemOverflowX = 0;
                dragItemOverflowY = 0;
            }
            break;

        case SDL_MOUSEMOTION:
            if (dragMode == FRAME_DRAG_CLIENT) {
                // Adjust coordinates to 0-based for client
                if (clientArea) {
                    clientEvent.motion.x -= clientX + scrollX;
                    clientEvent.motion.y -= clientY + scrollY;

                    // Propogate to client
                    clientArea->event(hasFocus, &clientEvent);
                }
            }
            else if (event->motion.state & SDL_BUTTON_LMASK) {
                // Tab?
                if ((dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) &&
                         (dragItem == FRAME_SCROLL_TAB)) {
                    int newX = hScrollbarTab + event->motion.xrel;

                    // Overflow
                    if (dragItemOverflowX) {
                        newX += dragItemOverflowX;
                        dragItemOverflowX = 0;
                    }
                    if (newX + hScrollbarTabSize > (hScrollbarWidth - scrollbarButtonSize * 2)) {
                        dragItemOverflowX = newX + hScrollbarTabSize - (hScrollbarWidth - scrollbarButtonSize * 2);
                        newX -= dragItemOverflowX;
                    }
                    if (newX < 0) {
                        dragItemOverflowX = newX;
                        newX = 0;
                    }
                    
                    // Scroll to right spot
                    scrollTo(newX * (clientWidthInner - clientActualWidth) / (hScrollbarWidth - (scrollbarButtonSize * 2) - hScrollbarTabSize), scrollY, newX, -1);
                }
                else if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) &&
                         (dragItem == FRAME_SCROLL_TAB)) {
                    int newY = vScrollbarTab + event->motion.yrel;

                    // Overflow
                    if (dragItemOverflowY) {
                        newY += dragItemOverflowY;
                        dragItemOverflowY = 0;
                    }
                    if (newY + vScrollbarTabSize > (vScrollbarHeight - scrollbarButtonSize * 2)) {
                        dragItemOverflowY = newY + vScrollbarTabSize - (vScrollbarHeight - scrollbarButtonSize * 2);
                        newY -= dragItemOverflowY;
                    }
                    if (newY < 0) {
                        dragItemOverflowY = newY;
                        newY = 0;
                    }
                    
                    // Scroll to right spot
                    scrollTo(scrollX, newY * (clientHeightInner - clientActualHeight) / (vScrollbarHeight - (scrollbarButtonSize * 2) - vScrollbarTabSize), -1, newY);
                }
                // Scrollbar?
                else if ((dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) ||
                         (dragMode == FRAME_DRAG_SCROLLBAR_VERT)) {
                    whereCoords(event->motion.x, event->motion.y, &newDragMode, &newDragItem);
                    if (dragMode == newDragMode) {
                        if (dragItem != newDragItem) {
                            dragItem = newDragItem;
                            setDirty();
                            if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                            else whyDirty |= FRAME_DIRTY_SCROLLVERT;
                        }
                    }
                    else {
                        if (dragItem != FRAME_SCROLL_NONE) {
                            dragItem = FRAME_SCROLL_NONE;
                            setDirty();
                            if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                            else whyDirty |= FRAME_DIRTY_SCROLLVERT;
                        }
                    }
                }
            }
            // Just hover?
            else {
                whereCoords(event->motion.x, event->motion.y, &newDragMode, &newDragItem);
                if (newDragMode == FRAME_DRAG_CLIENT) {
                    // Adjust coordinates to 0-based for client
                    if (clientArea) {
                        clientEvent.motion.x -= clientX + scrollX;
                        clientEvent.motion.y -= clientY + scrollY;
    
                        // Propogate to client
                        clientArea->event(hasFocus, &clientEvent);
                    }
                }
                else selectMouse(MOUSE_NORMAL);
            }
            break;
            
        case SDL_INPUTFOCUS:
            if ((event->user.code & 1) && (!haveFocus)) {
                haveFocus = 1;
                
                // Propogate to client
                if (clientArea) {
                    clientArea->event(hasFocus, &clientEvent);
                }
            }
            else if (!(event->user.code & 1) && (haveFocus)) {
                haveFocus = 0;

                // Propogate to client
                if (clientArea) {
                    clientArea->event(hasFocus, &clientEvent);
                }
            }
            break;
            
        case SDL_SPECIAL:
            // Scrollbar repeat?
            if ((event->user.code & SDL_IDLE) && (scrollRepeatDelay)) {
                ticks = SDL_GetTicks();
                // (catch wraparound)
                if (ticks < scrollRepeatTicks) scrollRepeatTicks = SDL_GetTicks();
                else if (ticks - scrollRepeatTicks > scrollRepeatDelay) {
                    scrollRepeatTicks = SDL_GetTicks();
                    scrollRepeatDelay = DELAY_SCROLLBAR_REPEATAGAIN;
                    if (dragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                        if (dragItem == FRAME_SCROLL_LESS) scrollTo(scrollX, scrollY + vScrollLine);
                        else if (dragItem == FRAME_SCROLL_MORE) scrollTo(scrollX, scrollY - vScrollLine);
                        else if (dragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX, scrollY + vScrollPage);
                        else if (dragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX, scrollY - vScrollPage);
                    }
                    if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                        if (dragItem == FRAME_SCROLL_LESS) scrollTo(scrollX + hScrollLine, scrollY);
                        else if (dragItem == FRAME_SCROLL_MORE) scrollTo(scrollX - hScrollLine, scrollY);
                        else if (dragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX + hScrollPage, scrollY);
                        else if (dragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX - hScrollPage, scrollY);
                    }
                    // Force mouse motion to determine new drag mode
                    SDL_GetMouseState(&mX, &mY);
                    SDL_WarpMouse(mX, mY);
                    idleUsed = 1;
                }
            }
            // (continue on, to propogate to client)
            
        case SDL_MOUSEFOCUS:
            // (we can just propogate MOUSEFOCUS, since we are virtually equal
            // to our child window in size anyways)
        case SDL_COMMAND:
        case SDL_QUIT:
        case SDL_SYSKEY:
        case SDL_OBJECTCHANGE:
            // (exempt window may exist)
            exempt = (Window*)clientEvent.user.data2;
        default:
            // Propogate to client
            if ((clientArea) && (clientArea != exempt)) {
                return (clientArea->event(hasFocus, &clientEvent) || idleUsed);
            }
            return idleUsed;
    }

    // All mouse and focus events, we use.
    return 1;
}

int WidgetScroll::stateTabId() const { start_func
    if (clientWidget) return clientWidget->stateTabId();
    return Widget::stateTabId();
}

int WidgetScroll::stateTabId(int wTabId) { start_func
    if (clientWidget) return clientWidget->stateTabId(wTabId);
    return Widget::stateTabId(wTabId);
}

int WidgetScroll::refuseAll() const { start_func
    if (clientWidget) return clientWidget->refuseAll();
    if (clientArea) return Widget::refuseAll();
    return 1;
}
    
void WidgetScroll::doAction() { start_func
    if (clientWidget) clientWidget->doAction();
    return;
}

char WidgetScroll::getShortcut() const { start_func
    if (clientWidget) return clientWidget->getShortcut();
    return 0;
}

int WidgetScroll::getId() const { start_func
    if (clientWidget) return clientWidget->getId();
    return Widget::getId();
}

void WidgetScroll::disable() { start_func
    if (clientWidget) clientWidget->disable();
    Widget::disable();
}

void WidgetScroll::enable() { start_func
    if (clientWidget) clientWidget->enable();
    Widget::enable();
}

void WidgetScroll::load() { start_func
    if (clientWidget) clientWidget->load();
}

void WidgetScroll::apply() { start_func
    if (clientWidget) clientWidget->apply();
}

void WidgetScroll::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
    Window::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
    if (clientArea != NULL) clientArea->resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
}

const char* WidgetScroll::tooltip(int xPos, int yPos) const { start_func
    if (clientWidget) {
        xPos -= clientX + scrollX;
        yPos -= clientY + scrollY;
        return clientWidget->tooltip(xPos, yPos);
    }
    return Widget::tooltip(xPos, yPos);
}


